#pragma once

#include <functional>
#include <type_traits>
#include <utility>

namespace zenox {

template <class T>
struct pod_uninitialized {
    static_assert(std::is_trivial_v<T>, "pod_uninitialized only suitable for trivial types");
    T value;

    constexpr pod_uninitialized() noexcept {}  // 不是 = default，也不写 : value()，这样一来只要 T 是 POD 类型，value 就不会0初始化
    constexpr pod_uninitialized(T &&value_) noexcept : value(std::move(value_)) {}  // 强制初始化的版本（T隐式转换为pod_uninitialized<T>）
    constexpr pod_uninitialized(T const &value_) noexcept(std::is_nothrow_copy_constructible_v<T>) : value(value_) {}
    template <class U, class = std::enable_if_t<std::is_constructible_v<T, U>>>
    explicit constexpr pod_uninitialized(U &&value_) noexcept(std::is_nothrow_constructible_v<T, U>) : value(value_) {}
    constexpr operator T const &() const & noexcept { return value; } // pod_uninitialized<T>隐式转换为T
    constexpr operator T &() & noexcept { return value; } // pod_uninitialized<T>隐式转换为T
    constexpr operator T const &&() const && noexcept { return std::move(value); } // pod_uninitialized<T>隐式转换为T
    constexpr operator T &&() && noexcept { return std::move(value); } // pod_uninitialized<T>隐式转换为T
    pod_uninitialized(pod_uninitialized const &) = default;
    pod_uninitialized &operator=(pod_uninitialized const &) = default;
    pod_uninitialized(pod_uninitialized &&) = default;
    pod_uninitialized &operator=(pod_uninitialized &&) = default;
};

template <class T>
pod_uninitialized(T) -> pod_uninitialized<T>;

template <class T = void>
struct explicit_tag {
    using type = T;

    explicit explicit_tag() = default;
};

template <class T = void>
struct implicit_tag {
    using type = T;

    implicit_tag() = default;
};

struct disable_copy {
    disable_copy(disable_copy &&) = delete;
    disable_copy &operator=(disable_copy &&) = delete;
    disable_copy(disable_copy const &) = delete;
    disable_copy &operator=(disable_copy const &) = delete;
};

struct disable_copy_allow_move {
    disable_copy_allow_move(disable_copy_allow_move &&) = default;
    disable_copy_allow_move &operator=(disable_copy_allow_move &&) = default;
    disable_copy_allow_move(disable_copy_allow_move const &) = delete;
    disable_copy_allow_move &operator=(disable_copy_allow_move const &) = delete;
};

template <class T>
struct value_initialized { // in C++ standard, the term 'value-initialization' means initialize POD type values to zero
    static_assert(std::is_trivial_v<T>, "value_initialized only suitable for trivial types");
    T value{};

    value_initialized() = default;
    constexpr value_initialized(T value_) noexcept : value(std::move(value_)) {}
    constexpr operator T const &() const & { return value; }
    constexpr operator T &() & { return value; }
    constexpr operator T const &&() const && { return std::move(value); }
    constexpr operator T &&() && { return std::move(value); }
};

template <class T>
value_initialized(T) -> value_initialized<T>;

template <class T>
struct value_initialize_on_move {
    static_assert(std::is_trivial_v<T>, "value_initialize_on_move only suitable for trivial types");
    T value{};

    value_initialize_on_move() = default;
    constexpr value_initialize_on_move(T value_) noexcept : value(std::move(value_)) {}
    constexpr operator T const &() const & noexcept { return value; }
    constexpr operator T &() & noexcept { return value; }
    constexpr operator T const &&() const && noexcept { return std::move(value); }
    constexpr operator T &&() && noexcept { return std::move(value); }

    constexpr value_initialize_on_move(value_initialize_on_move &&that) noexcept
    : value(std::exchange(that.value, T{}))
    {}

    constexpr value_initialize_on_move &operator=(value_initialize_on_move &&that) noexcept {
        if (this != std::addressof(that)) [[likely]] {
            value = std::exchange(that.value, T{});
        }
        return *this;
    }

    value_initialize_on_move(value_initialize_on_move const &) = delete;
    value_initialize_on_move &operator=(value_initialize_on_move const &) = delete;
};

template <class T>
value_initialize_on_move(T) -> value_initialize_on_move<T>;

template <class Func = std::function<void()>>
struct scope_exit {
private:
    static_assert(std::is_same_v<std::decay_t<Func>, Func>, "Func must be cv-unqualified");
    static_assert(std::is_invocable_v<Func>, "Func must be callable with no arguments");

    Func func;
    bool enabled{false};

public:
    scope_exit() = default;

    constexpr scope_exit(Func func) noexcept : func(std::move(func)), enabled(true) {
    }

    constexpr bool has_value() const noexcept {
        return enabled;
    }

    constexpr void release() noexcept { // turn off the alarm but not activate it
        enabled = false;
    }

    constexpr void reset() noexcept(noexcept(func())) { // activate the alarm and turn off it
        if (enabled) {
            func();
            enabled = false;
        }
    }

    constexpr ~scope_exit() noexcept(noexcept(func())) {
        if (enabled)
            func();
    }

    scope_exit(scope_exit const &) = delete;
    scope_exit &operator=(scope_exit const &) = delete;

    constexpr scope_exit(scope_exit &&that) noexcept : func(std::move(that.func)), enabled(that.enabled) {
        that.enabled = false;
    }

    constexpr scope_exit &operator=(scope_exit &&that) noexcept {
        if (this != &that) {
            enabled = that.enabled;
            that.enabled = false;
            func = std::move(that.func);
        }
        return *this;
    }
};

template <class Func>
scope_exit(Func) -> scope_exit<Func>;

/* template <class T> */
/* struct optionally_initialized { */
/* private: */
/*     static_assert(std::is_same_v<std::decay_t<T>, T>, "T must be cv-unqualified"); */
/*  */
/*     union { */
/*         T value; */
/*     }; */
/*     bool enabled{false}; */
/*  */
/* public: */
/*     optionally_initialized() = default; */
/*  */
/*     constexpr optionally_initialized(T value) */
/*     noexcept(noexcept(std::is_nothrow_move_constructible_v<T>)) */
/*     : value(std::move(value)), enabled(true) */
/*     {} */
/*  */
/*     constexpr bool has_value() const noexcept { */
/*         return enabled; */
/*     } */
/*  */
/*     T &get() noexcept { */
/*         return value; */
/*     } */
/*  */
/*     T const &get() const noexcept { */
/*         return value; */
/*     } */
/*  */
/*     void release() noexcept { */
/*         enabled = false; */
/*     } */
/*  */
/*     template <class ...Ts> */
/*     explicit optionally_initialized(std::in_place_t, Ts &&...ts) */
/*     noexcept(noexcept(std::is_nothrow_destructible_v<T>) && noexcept(std::is_nothrow_constructible_v<T, Ts...>)) */
/*     { */
/*         std::construct_at(std::addressof(value), std::forward<Ts>(ts)...); */
/*         enabled = true; */
/*     } */
/*  */
/*     template <class ...Ts> */
/*     T &emplace(Ts &&...ts) */
/*     noexcept(noexcept(std::is_nothrow_destructible_v<T>) && noexcept(std::is_nothrow_constructible_v<T, Ts...>)) */
/*     { */
/*         if (enabled) { */
/*             value.~T(); */
/*             enabled = false; */
/*         } */
/*         std::construct_at(std::addressof(value), std::forward<Ts>(ts)...); */
/*         enabled = true; */
/*         return value; */
/*     } */
/*  */
/*     void reset() */
/*     noexcept(noexcept(std::is_nothrow_destructible_v<T>)) */
/*     { */
/*         if (enabled) { */
/*             value.~T(); */
/*             enabled = false; */
/*         } */
/*     } */
/*  */
/*     ~optionally_initialized() */
/*     noexcept(noexcept(std::is_nothrow_destructible_v<T>)) */
/*     { */
/*         if (enabled) */
/*             value.~T(); */
/*     } */
/*  */
/*     optionally_initialized(optionally_initialized &&that) */
/*     noexcept(noexcept(std::is_nothrow_move_constructible_v<T>)) */
/*     : value(std::move(that.value)), enabled(that.enabled) */
/*     { */
/*         that.enabled = false; */
/*     } */
/*  */
/*     optionally_initialized &operator=(optionally_initialized &&that) */
/*     noexcept(noexcept(std::is_nothrow_move_assignable_v<T>)) */
/*     { */
/*         if (this != &that) [[likely]] { */
/*             value = std::move(that.value); */
/*             enabled = that.enabled; */
/*             that.enabled = false; */
/*         } */
/*         return *this; */
/*     } */
/*  */
/*     optionally_initialized(optionally_initialized const &that) */
/*     noexcept(noexcept(std::is_nothrow_copy_constructible_v<T>)) */
/*     : value(that.value), enabled(that.enabled) { */
/*     } */
/*  */
/*     optionally_initialized &operator=(optionally_initialized const &that) */
/*     noexcept(noexcept(std::is_nothrow_copy_assignable_v<T>)) */
/*     { */
/*         if (this != &that) [[likely]] { */
/*             enabled = that.enabled; */
/*             value = that.value; */
/*         } */
/*         return *this; */
/*     } */
/* }; */
/*  */
/* template <class T> */
/* optionally_initialized(T) -> optionally_initialized<T>; */

template <class T>
struct manually_initialized {
private:
    static_assert(std::is_same_v<std::decay_t<T>, T>, "T must be cv-unqualified");

    union {
        T value;
    };

public:
    manually_initialized() = default;

    constexpr manually_initialized(T value)
    noexcept(noexcept(std::is_nothrow_move_constructible_v<T>))
    : value(std::move(value))
    {}

    T &get() noexcept {
        return value;
    }

    T const &get() const noexcept {
        return value;
    }

    void reset()
    noexcept(noexcept(std::is_nothrow_destructible_v<T>))
    {
        value.~T();
    }

    template <class ...Ts>
    explicit manually_initialized(std::in_place_t, Ts &&...ts)
    noexcept(noexcept(std::is_nothrow_destructible_v<T>) && noexcept(std::is_nothrow_constructible_v<T, Ts...>))
    {
        std::construct_at(std::addressof(value), std::forward<Ts>(ts)...);
    }

    template <class ...Ts>
    T &emplace(Ts &&...ts)
    noexcept(noexcept(std::is_nothrow_destructible_v<T>) && noexcept(std::is_nothrow_constructible_v<T, Ts...>))
    {
        std::construct_at(std::addressof(value), std::forward<Ts>(ts)...);
        return value;
    }

    ~manually_initialized() = default;

    manually_initialized(manually_initialized &&that)
    noexcept(noexcept(std::is_nothrow_move_constructible_v<T>))
    : value(std::move(that.value))
    {
    }

    manually_initialized &operator=(manually_initialized &&that)
    noexcept(noexcept(std::is_nothrow_move_assignable_v<T>))
    {
        if (this != &that) [[likely]] {
            value = std::move(that.value);
        }
        return *this;
    }

    manually_initialized(manually_initialized const &that)
    noexcept(noexcept(std::is_nothrow_copy_constructible_v<T>))
    : value(that.value) {
    }

    manually_initialized &operator=(manually_initialized const &that)
    noexcept(noexcept(std::is_nothrow_copy_assignable_v<T>))
    {
        if (this != &that) [[likely]] {
            value = that.value;
        }
        return *this;
    }
};

template <class T>
manually_initialized(T) -> manually_initialized<T>;

}
